package com.cqw.robot.service;

import cn.hutool.core.util.ObjectUtil;
import com.cqw.robot.base.BasePath;
import com.cqw.robot.utils.ImageFindDemo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.apache.commons.io.IOUtils;
import org.junit.platform.commons.util.StringUtils;
import org.opencv.core.*;
import org.opencv.core.Point;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.utils.Converters;

import javax.activation.URLDataSource;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import static org.opencv.imgproc.Imgproc.MORPH_RECT;

/**
 * 读取答题卡服务层
 *
 * @author liupengFei
 * @date 2020/12/21
 */
@Slf4j
public class ReadCardServiceImpl {

    /**
     * 通过url获取图像并转为Mat
     * 支持网络图片读取, 但是有图片大小限制
     *
     * @param path the path
     * @return the Mat
     */
    public Mat getImageByPath(String path) {
        log.info("getImageByPath - 通过url获取图像并转为Mat. path = {}", path);

        if (StringUtils.isBlank(path)) {
            log.info("图片路径不能为空");
        }
        InputStream is = null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        URL url = null;
        try {
            url = new URL(path);
            is = url.openStream();
            byte[] buffer = new byte[8192];
            int bytesRead = 0;
            while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            Mat encoded = new Mat(1, os.size(), CvType.CV_8U);
            encoded.put(0, 0, os.toByteArray());
            os.close();
            //从内存中读，返回Mat形式
            Mat decoded = Imgcodecs.imdecode(encoded, 1);
            Imgcodecs.imwrite("D://opencv//decoded.jpg", decoded);
            encoded.release();
            return decoded.clone();
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            throw new IllegalArgumentException(e.getLocalizedMessage());
        }
    }

    /**
     * 通过url获取图像并转为Mat
     * 支持网络图片读取, 但是效率低
     *
     * @param path the path
     * @return the Mat
     */
    public Mat getImageByPath2(String path) {
        log.info("getImageByPath2 - 通过url获取图像并转为Mat. path = {}", path);

        if (StringUtils.isBlank(path)) {
            log.info("图片路径不能为空");
        }
        try {
//            以下这个方式可行，就是效率低，待考虑
            URLDataSource dataSource = new URLDataSource(new URL(path));
            InputStream stream = dataSource.getInputStream();
            byte[] bytes = IOUtils.toByteArray(stream);
            Mat mat = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_COLOR);
            Imgcodecs.imwrite("D://opencv//mat.jpg", mat);
            return mat.clone();
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            throw new IllegalArgumentException(e.getLocalizedMessage());
        }
    }




    /**
     * 调整图像至标准图像
     *
     * @param anchorPoint the anchorPoint
     * @param standard    the standard
     * @param adjust      the adjust
     * @return the 校正之后图像路径
     */
    public static Mat adjustMat(Mat anchorPoint, Mat standard, Mat adjust) {
        log.info("adjustMat - 调整图像至标准图像. anchorPoint = {}, standard = {}, adjust = {}", anchorPoint, standard, adjust);

        // 获取标准图像锚点
        List<Point> standardPointList = fetchAnchorPoints(standard, anchorPoint);
        Mat standardMat = Converters.vector_Point2f_to_Mat(standardPointList);
        // 获取待调整图像锚点
        List<Point> correctPointList = fetchAnchorPoints(adjust, anchorPoint);
        Mat adjustMat = Converters.vector_Point2f_to_Mat(correctPointList);
        // 获取 3*3 矩阵
        Mat perspectiveTransform = Imgproc.getPerspectiveTransform(adjustMat, standardMat);
        Mat resultMat = new Mat();
        Imgproc.warpPerspective(adjust, resultMat, perspectiveTransform, new Size(standard.width(), standard.height()), Imgproc.INTER_CUBIC);
        return resultMat;
    }


    /**
     * 寻找锚点
     *
     * @param img    待查找图像
     * @param anchor 锚点图像
     * @return 锚点集合
     */
    public static List<Point> fetchAnchorPoints(Mat img, Mat anchor) {
        log.info("fetchAnchorPoints - 寻找锚点. img = {}, anchor = {}", img, anchor);

        List<Point> list = new ArrayList<Point>();
        // 宽 x
        int width = img.width();
        // 高 y
        int height = img.height();
        int halfWidth = width / 2;
        int halfHeight = height / 2;
        // 左上角为(0,0) 横向为x, 纵向为y
        // 如果把图片整个看成数学上的四个象限,这是第二象限
        Mat src1 = img.submat(new Rect(0, 0, halfWidth, halfHeight));
        // 如果把图片整个看成数学上的四个象限,这是第一象限
        Mat src2 = img.submat(new Rect(halfWidth, 0, halfWidth, halfHeight));
        // 如果把图片整个看成数学上的四个象限,这是第三象限
        Mat src3 = img.submat(new Rect(0, halfHeight, halfWidth, halfHeight));
        // 如果把图片整个看成数学上的四个象限,这是第四象限
        Mat src4 = img.submat(new Rect(halfWidth, halfHeight, halfWidth, halfHeight));
        Mat imagematch = new Mat();
        Point point1, point2, point3, point4;

        Imgproc.cvtColor( src1, imagematch, 5);
        Core.normalize(imagematch, imagematch);
        Core.MinMaxLocResult minMaxLocResult1 = Core.minMaxLoc(imagematch);
        point1 = minMaxLocResult1.maxLoc;

        Imgproc.cvtColor( src2, imagematch, 5);
        Core.normalize(imagematch, imagematch);
        Core.MinMaxLocResult minMaxLocResult2 = Core.minMaxLoc(imagematch);
        point2 = new Point(minMaxLocResult2.maxLoc.x + halfWidth, minMaxLocResult2.maxLoc.y);

        Imgproc.cvtColor( src3, imagematch, 5);
        Core.normalize(imagematch, imagematch);
        Core.MinMaxLocResult minMaxLocResult3 = Core.minMaxLoc(imagematch);
        point3 = new Point(minMaxLocResult3.maxLoc.x, minMaxLocResult3.maxLoc.y + halfHeight);

        Imgproc.cvtColor( src4, imagematch, 5);
        Core.normalize(imagematch, imagematch);
        Core.MinMaxLocResult minMaxLocResult4 = Core.minMaxLoc(imagematch);
        point4 = new Point(minMaxLocResult4.maxLoc.x + halfWidth, minMaxLocResult4.maxLoc.y + halfHeight);

        list.add(point1);
        list.add(point2);
        list.add(point3);
        list.add(point4);
        return list;
    }

    /**
     * 横向获取区域内波峰
     *
     * @param img     待查找图像
     * @param obscure 是否进行膨胀腐蚀
     * @return the Integer Vector
     */
    public Vector<Integer> fetchMaxContourX(Mat img, Boolean obscure) {
        log.info("fetchMaxContourX - 横向获取区域内波峰. img = {}, obscure = {}", img, obscure);

        //读取图像
        Mat testmat = this.blurryImg(img, obscure);
        //计算纵向投影
        Vector<Integer> vcol = new Vector<Integer>();
        int itmp = 0;
        for (int i = 0; i < testmat.cols(); i++) {
            for (int j = 0; j < testmat.rows(); j++) {
                double[] doubles = testmat.get(j, i);
                if (doubles[0] > 0.0) {
                    itmp = itmp + 1;
                }
            }
            vcol.add(itmp);
            itmp = 0;
        }
        //对得到的结果进行处理,计算波峰
        return getCrest(vcol);
    }

    /**
     * 纵向获取区域内波峰
     *
     * @param img     待查找图像
     * @param obscure 是否进行膨胀腐蚀
     * @return the Integer Vector
     */
    public Vector<Integer> fetchMaxContourY(Mat img, Boolean obscure) {
        log.info("fetchMaxContourY - 纵向获取区域内波峰. img = {}, obscure = {}", img, obscure);

        Mat testmat = this.blurryImg(img, obscure);
        //计算横向投影
        Vector<Integer> vcol = new Vector<Integer>();
        int itmp = 0;
        for (int j = 0; j < testmat.rows(); j++) {
            for (int i = 0; i < testmat.cols(); i++) {
                double[] doubles = testmat.get(j, i);
                if (doubles[0] > 0.0) {
                    itmp = itmp + 1;
                }
            }
            vcol.add(itmp);
            itmp = 0;
        }
        //对得到的结果进行处理,计算波峰
        return this.getCrest(vcol);
    }

    /**
     * mat转成成灰度图像
     *
     * @param img the img
     * @return the 灰度图像
     */
    public Mat mat2Grayscale(Mat img) {
        log.info("mat2Grayscale - mat转成成灰度图像. img = {}", img);

        Mat result = new Mat();
        Imgproc.cvtColor(img, result, Imgproc.COLOR_BGR2GRAY);
        return result;
    }

    /**
     * 图像进行二值化，并腐蚀膨胀
     *
     * @param img     the img
     * @param obscure 是否进行膨胀腐蚀
     * @return the Mat
     */
    private Mat blurryImg(Mat img, Boolean obscure) {
        log.info("blurry - 图像进行二值化，并腐蚀膨胀 img = {}, obscure = {}", img, obscure);

        if (ObjectUtil.isNull(obscure)) {
            obscure = Boolean.FALSE;
        }
        //读取图像
        Mat testmat = img.clone();
        //大津法找到敏感区域
        Imgproc.threshold(testmat, testmat, 0, 255, Imgproc.THRESH_OTSU);
        //为什么要转换，因为白色是有数据的区域,轮廓是围绕白色区域的
        Imgproc.threshold(testmat, testmat, 0, 255, Imgproc.THRESH_BINARY_INV);
        if (obscure) {
            //确定腐蚀和膨胀核的大小
            Mat element = Imgproc.getStructuringElement(MORPH_RECT, new Size(3, 3));
            //腐蚀操作
            Imgproc.erode(testmat, testmat, element);
            //膨胀操作
            Imgproc.dilate(testmat, testmat, element);
        }
        return testmat;
    }

    /**
     * 获取波峰
     *
     * @param vector the vector
     * @return the Integer Vector
     */
    private Vector<Integer> getCrest(Vector<Integer> vector) {
        log.info("getCrest - 获取波峰 vector = {}", vector);

        Vector<Integer> vrise = new Vector<Integer>();
        for (Integer i = 1; i < vector.size(); i++) {
            if (vector.get(i - 1) == 0 && vector.get(i) > 0) {
                vrise.add(i);
            }
        }
        return vrise;
    }


    public static Mat getMatByLocalPath(String path, Boolean isChange) {
        log.info("getMatByLocalPath - 根据本地路径获取Mat. path = {}, isChange = {}", path, isChange);

        if (ObjectUtil.isNull(isChange)) {
            isChange = Boolean.FALSE;
        }
        if (StringUtils.isBlank(path)) {
            log.info("图片路径不能为空");
        }
        if (isChange) {
            String userDir = System.getProperty("user.dir");
            String filePath = userDir + path;
            return Imgcodecs.imread(filePath, 0);
        }
        return Imgcodecs.imread(BasePath.getImgPath() + path);
    }


    /**
     * 图像格式转换：BufferedImage -> Mat
     *
     * @param
     * @return {@link Mat}
     */
    public static Mat bufferedImageToMat(BufferedImage bi1) {
        BufferedImage bi2= new BufferedImage(bi1.getWidth(),bi1.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        Mat mat = new Mat(bi2.getHeight(), bi2.getWidth(), CvType.CV_8UC3);
        byte[] data = ((DataBufferByte) bi2.getRaster().getDataBuffer()).getData();
        mat.put(0, 0, data);
        return mat;
    }



    /**
     * 全屏截图
     * @return 返回BufferedImage
     */
    public static BufferedImage getFullScreenShot() {
        BufferedImage bfImage = null;
        int width = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        int height = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        try {
            Robot robot = new Robot();
            bfImage = robot.createScreenCapture(new Rectangle(0, 0, width, height));
        } catch (AWTException e) {
            e.printStackTrace();
        }
        return bfImage;
    }



    public static void main(String[] args) {

        System.loadLibrary( Core.NATIVE_LIBRARY_NAME );
        // 锚点图像路径
        String anchorPath = "/images/4file.png";
        // 读取图片
        testMatching(anchorPath);
    }




    public static void testMatching(String targetImg) {

        // 1.导入Opencv库
        OpenCV.loadShared();
        // 2.加载图像
        Mat src = bufferedImageToMat(getFullScreenShot());// 待匹配图片
        Mat template = Imgcodecs.imread(targetImg);// 获取匹配模板

        // 3.进行模板匹配
        // 创建一个输出图像
        Mat outputImage = new Mat(src.rows(), src.cols(), src.type());
        Imgproc.cvtColor(template, outputImage, Imgproc.TM_CCOEFF_NORMED);

        // 4.获取匹配结果,查找最大匹配值
        Core.MinMaxLocResult result = Core.minMaxLoc(outputImage);
        Point matchLoc = result.maxLoc;
        double similarity = result.maxVal; //匹配度
        int x = (int) matchLoc.x; //小图大大图中的x坐标
        int y = (int) matchLoc.y; //小图大大图中的y坐标
        System.out.println(x + "," + y + " similarity: " + similarity);
        //将查找到的结果标上框框
        Imgproc.rectangle(src,new Point(x,y),new Point(x+template.cols(),y+template.rows()),
                new Scalar( 0, 0, 255),2);

        //5.显示结果
        HighGui.imshow("模板匹配", src);
        HighGui.waitKey();

    }

}
